package com.pt25.base.dep.redis;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.pt25.base.util.StrUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.client.codec.StringCodec;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * redis本地缓存service
 */
@Slf4j
@Service
public class RedisLocalCacheService {

    //本地缓存默认值
    private static final String localCacheDefaultValue = "~@#$%^&*~";
    private static final Integer sixtySeconds = 60;

    private final ConcurrentHashMap<String, Cache<String, Object>> strLocalCacheMap = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Cache<String, Map<Object, Object>>> hashLocalCacheMap = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Cache<String, Set<Object>>> setLocalCacheMap = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Cache<String, List<Object>>> listLocalCacheMap = new ConcurrentHashMap<>();

    @Resource
    private RedisService redisService;

    /**
     * @param redisName                redis集群名称
     * @param cacheKey                 redis缓存key
     * @param localCacheExpiredSeconds 本地缓存过期时间
     * @return 缓存数据
     */
    public Object getStrLocalCache(String redisName, String cacheKey, Integer localCacheExpiredSeconds) {
        checkLocalCacheParams(cacheKey, localCacheExpiredSeconds);
        String localCacheTypeKey = getLocalCacheTypeKey(redisName, "str", localCacheExpiredSeconds);
        Cache<String, Object> localCache = getStrLocalCacheMap(localCacheTypeKey, localCacheExpiredSeconds);
        Object cacheData = localCache.getIfPresent(cacheKey);
        if (null == cacheData) {
            Object cacheObj = redisService.getRedissonClient(redisName).getBucket(cacheKey, new StringCodec()).get();
            localCache.put(cacheKey, Objects.requireNonNullElse(cacheObj, localCacheDefaultValue));
            cacheData = localCache.getIfPresent(cacheKey);
        }
        return localCacheDefaultValue.equals(cacheData) ? null : cacheData;
    }

    public Object getStrLocalCache(String cacheKey) {
        return getStrLocalCache(null, cacheKey, sixtySeconds);
    }

    public Object getStrLocalCache(String redisName, String cacheKey) {
        return getStrLocalCache(redisName, cacheKey, sixtySeconds);
    }



    /**
     * @param redisName                redis集群名称
     * @param cacheKey                 redis缓存key
     * @param localCacheExpiredSeconds 本地缓存过期时间
     * @return 缓存数据
     */
    public Map<Object, Object> getMapLocalCache(String redisName, String cacheKey, Integer localCacheExpiredSeconds) {
        checkLocalCacheParams(cacheKey, localCacheExpiredSeconds);
        String localCacheTypeKey = getLocalCacheTypeKey(redisName, "hash", localCacheExpiredSeconds);
        Cache<String, Map<Object, Object>> localCache = getHashLocalCacheMap(localCacheTypeKey, localCacheExpiredSeconds);
        Map<Object, Object> cacheData = localCache.getIfPresent(cacheKey);
        if (null == cacheData) {
            Map<Object, Object> objectObjectMap = redisService.getRedissonClient(redisName).getMap(cacheKey, new StringCodec()).readAllMap();
            localCache.put(cacheKey, Objects.requireNonNullElseGet(objectObjectMap, HashMap::new));
            return localCache.getIfPresent(cacheKey);
        }
        return cacheData;
    }

    public Map<Object, Object> getMapLocalCache(String cacheKey) {
        return getMapLocalCache(null, cacheKey, sixtySeconds);
    }

    public Map<Object, Object> getMapLocalCache(String redisName, String cacheKey) {
        return getMapLocalCache(redisName, cacheKey, sixtySeconds);
    }



    /**
     * @param redisName                redis集群名称
     * @param cacheKey                 redis缓存key
     * @param dataKey                  redis map中的key
     * @param localCacheExpiredSeconds 本地缓存过期时间
     * @return 缓存数据
     */
    public Object getMapDataLocalCache(String redisName, String cacheKey, String dataKey, Integer localCacheExpiredSeconds) {
        checkLocalCacheParams(cacheKey, localCacheExpiredSeconds);
        if (StrUtil.isBlank(dataKey)) {
            throw new IllegalArgumentException("dataKey is null or empty");
        }
        String localCacheTypeKey = getLocalCacheTypeKey(redisName, "hash", localCacheExpiredSeconds);
        Cache<String, Object> localCache = getStrLocalCacheMap(localCacheTypeKey, localCacheExpiredSeconds);
        String localCacheKey = cacheKey + "&" + dataKey;
        Object cacheData = localCache.getIfPresent(localCacheKey);
        if (null == cacheData) {
            Object objectObject = redisService.getRedissonClient(redisName).getMap(cacheKey, new StringCodec()).get(dataKey);
            localCache.put(localCacheKey, Objects.requireNonNullElse(objectObject, localCacheDefaultValue));
            cacheData = localCache.getIfPresent(localCacheKey);
        }
        return localCacheDefaultValue.equals(cacheData) ? null : cacheData;
    }

    public Object getMapDataLocalCache(String cacheKey, String dataKey) {
        return getMapDataLocalCache(null, cacheKey, dataKey, sixtySeconds);
    }

    public Object getMapDataLocalCache(String redisName, String cacheKey, String dataKey) {
        return getMapDataLocalCache(redisName, cacheKey, dataKey, sixtySeconds);
    }



    /**
     * @param redisName                redis集群名称
     * @param cacheKey                 redis缓存key
     * @param localCacheExpiredSeconds 本地缓存过期时间
     * @return 缓存数据
     */
    public Set<Object> getSetLocalCache(String redisName, String cacheKey, Integer localCacheExpiredSeconds) {
        checkLocalCacheParams(cacheKey, localCacheExpiredSeconds);
        String localCacheTypeKey = getLocalCacheTypeKey(redisName, "set", localCacheExpiredSeconds);
        Cache<String, Set<Object>> localCache = getSetLocalCacheMap(localCacheTypeKey, localCacheExpiredSeconds);
        Set<Object> cacheData = localCache.getIfPresent(cacheKey);
        if (null == cacheData) {
            Set<Object> objects = redisService.getRedissonClient(redisName).getSet(cacheKey, new StringCodec()).readAll();
            localCache.put(cacheKey, Objects.requireNonNullElseGet(objects, HashSet::new));
            return localCache.getIfPresent(cacheKey);
        }
        return cacheData;
    }

    public Set<Object> getSetLocalCache(String cacheKey) {
        return getSetLocalCache(null, cacheKey, sixtySeconds);
    }

    public Set<Object> getSetLocalCache(String redisName, String cacheKey) {
        return getSetLocalCache(redisName, cacheKey, sixtySeconds);
    }

    /**
     * @param redisName                redis集群名称
     * @param cacheKey                 redis缓存key
     * @param localCacheExpiredSeconds 本地缓存过期时间
     * @return 缓存数据
     */
    public List<Object> getListLocalCache(String redisName, String cacheKey, Integer localCacheExpiredSeconds) {
        checkLocalCacheParams(cacheKey, localCacheExpiredSeconds);
        String localCacheTypeKey = getLocalCacheTypeKey(redisName, "list", localCacheExpiredSeconds);
        Cache<String, List<Object>> localCache = getListLocalCacheMap(localCacheTypeKey, localCacheExpiredSeconds);
        List<Object> cacheData = localCache.getIfPresent(cacheKey);
        if (null == cacheData) {
            List<Object> objects = redisService.getRedissonClient(redisName).getList(cacheKey, new StringCodec()).readAll();
            localCache.put(cacheKey, Objects.requireNonNullElse(objects, Collections.emptyList()));
            return localCache.getIfPresent(cacheKey);
        }
        return cacheData;
    }

    public List<Object> getListLocalCache(String cacheKey) {
        return getListLocalCache(null, cacheKey, sixtySeconds);
    }


    public List<Object> getListLocalCache(String redisName, String cacheKey) {
        return getListLocalCache(redisName, cacheKey, sixtySeconds);
    }

    private Cache<String, Object> getStrLocalCacheMap(String localCacheTypeKey, Integer localCacheExpiredSeconds) {
        return getLocalCacheMap(strLocalCacheMap, localCacheTypeKey, localCacheExpiredSeconds);
    }

    private Cache<String, Map<Object, Object>> getHashLocalCacheMap(String localCacheTypeKey, Integer localCacheExpiredSeconds) {
        return getLocalCacheMap(hashLocalCacheMap, localCacheTypeKey, localCacheExpiredSeconds);
    }

    private Cache<String, Set<Object>> getSetLocalCacheMap(String localCacheTypeKey, Integer localCacheExpiredSeconds) {
        return getLocalCacheMap(setLocalCacheMap, localCacheTypeKey, localCacheExpiredSeconds);
    }

    private Cache<String, List<Object>> getListLocalCacheMap(String localCacheTypeKey, Integer localCacheExpiredSeconds) {
        return getLocalCacheMap(listLocalCacheMap, localCacheTypeKey, localCacheExpiredSeconds);
    }

    private <K, V> Cache<K, V> getLocalCacheMap(ConcurrentHashMap<String, Cache<K, V>> localCacheMap, String localCacheTypeKey, Integer localCacheExpiredSeconds) {
        Cache<K, V> localCache = localCacheMap.get(localCacheTypeKey);
        if (null != localCache) return localCache;
        localCacheMap.putIfAbsent(localCacheTypeKey, CacheBuilder.newBuilder()
                .expireAfterWrite(localCacheExpiredSeconds, TimeUnit.SECONDS)
                .build());
        return localCacheMap.get(localCacheTypeKey);
    }

    private String getLocalCacheTypeKey(String redisName, String cacheType, Integer cacheExpiredSeconds) {
        redisName = null == redisName ? "default" : redisName;
        return redisName + "_" + cacheType + "_" + cacheExpiredSeconds;
    }

    private void checkLocalCacheParams(String cacheKey, Integer localCacheExpiredSeconds) {
        if (StrUtil.isBlank(cacheKey)) {
            throw new IllegalArgumentException("cacheKey is null or empty");
        }
        if (null == localCacheExpiredSeconds || localCacheExpiredSeconds <= 0) {
            throw new IllegalArgumentException("localCacheExpiredSeconds is less than 0");
        }
    }
}
